package com.tuniu.agents.common.logger;

import com.tuniu.agents.message.MessageLogger;
import com.tuniu.agents.message.MessageSender;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.context.ApplicationContext;
import org.springframework.util.StringUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

@Slf4j
public abstract class LoggerAspect {

    protected ApplicationContext applicationContext;
    protected MessageSender messageSender;

    public LoggerAspect(ApplicationContext applicationContext, MessageSender messageSender) {
        this.applicationContext = applicationContext;
        this.messageSender = messageSender;
    }

    public abstract String getConversationId(Object[] args);

    // Add the result to the end of the argument array
    protected Object[] addResultToArgs(Object[] args, Object result) {
        Object[] newArgs = new Object[args.length + 1];
        System.arraycopy(args, 0, newArgs, 0, args.length);
        newArgs[args.length] = result;
        return newArgs;
    }

    // Execute the specified Spring Bean method
    protected List<String> executeSpringBeanMethod(String beanName, String methodName, Object[] args) {
        if (StringUtils.isEmpty(beanName) || StringUtils.isEmpty(methodName)) {
            log.warn("Bean name or method name is empty, unable to execute method call");
            return Collections.emptyList();
        }

        try {
            // Check if the bean exists
            if (!applicationContext.containsBean(beanName)) {
                log.error("Bean does not exist: {}", beanName);
                return Collections.emptyList();
            }

            Object bean = applicationContext.getBean(beanName);
            Method[] methods = bean.getClass().getMethods();

            // Find the matching method
            Optional<Method> matchedMethod = Arrays.stream(methods)
                    .filter(m -> m.getName().equals(methodName))
                    .filter(m -> m.getParameterCount() == 0 || m.getParameterCount() == args.length)
                    .findFirst();

            if (matchedMethod.isPresent()) {
                Method method = matchedMethod.get();
                Object result;

                // Invoke the method and ensure the return type is correct
                if (method.getParameterCount() == 0) {
                    result = method.invoke(bean);
                } else {
                    result = method.invoke(bean, args);
                }

                if (result instanceof List) {
                    return (List<String>) result;
                } else {
                    log.warn("Method {}.{} return type is not List<String>: {}",
                            beanName, methodName, result != null ? result.getClass().getName() : "null");
                    return Collections.emptyList();
                }
            } else {
                log.error("No matching method found: {}.{} Parameter count: {}", beanName, methodName, args.length);
                return Collections.emptyList();
            }
        } catch (InvocationTargetException e) {
            // Exception thrown by the target method
            log.error("Exception thrown by target method while executing Bean method: {}.{}", beanName, methodName, e.getTargetException());
        } catch (Exception e) {
            log.error("Error while executing Bean method: {}.{}", beanName, methodName, e);
        }

        return Collections.emptyList();
    }

    protected Object getResult(ProceedingJoinPoint joinPoint, MessageLogger logger) throws Throwable {
        String conversationId = getConversationId(joinPoint.getArgs());
        // If the annotation is found
        if (StringUtils.hasText(conversationId) && logger != null) {

            // Print fixed string
            if (StringUtils.hasText(logger.getBeforeMessage())) {
                messageSender.send(conversationId, logger.getBeforeMessage());
            }

            // Execute the specified Spring Bean method
            if (StringUtils.hasText(logger.getBeanName()) && StringUtils.hasText(logger.getBeforeMethod())) {
                List<String> messages = executeSpringBeanMethod(logger.getBeanName(), logger.getBeforeMethod(), joinPoint.getArgs());
                if (messages != null && messages.size() > 0) {
                    for (String message : messages) {
                        messageSender.send(conversationId, message);
                    }
                }
            }
        }
        Object result = null;
        try {
            // Execute the original method
            result = joinPoint.proceed();
        } catch (Throwable e) {
            // Handle the exception from joinPoint.proceed()
            if (StringUtils.hasText(conversationId) && logger != null) {
                // Print fixed string
                if (StringUtils.hasText(logger.getThrowableMessage())) {
                    messageSender.send(conversationId, logger.getThrowableMessage());
                }

                // Execute the specified Spring Bean method
                if (StringUtils.hasText(logger.getBeanName()) && StringUtils.hasText(logger.getThrowableMethod())) {
                    List<String> messages = executeSpringBeanMethod(logger.getBeanName(), logger.getThrowableMethod(), joinPoint.getArgs());
                    if (messages != null && messages.size() > 0) {
                        for (String message : messages) {
                            messageSender.send(conversationId, message);
                        }
                    }
                }
            }

            // Rethrow the exception for the caller to handle
            throw e;
        }

        // If the annotation is found
        if (StringUtils.hasText(conversationId) && logger != null) {

            // Print fixed string
            if (StringUtils.hasText(logger.getAfterMessage())) {
                messageSender.send(conversationId, logger.getAfterMessage());
            }

            // Execute the specified Spring Bean method (after), passing the original method result
            if (StringUtils.hasText(logger.getBeanName()) && StringUtils.hasText(logger.getAfterMethod())) {
                // Create a new argument array containing the original arguments and the result
                Object[] argsWithResult = addResultToArgs(joinPoint.getArgs(), result);
                List<String> messages = executeSpringBeanMethod(logger.getBeanName(), logger.getAfterMethod(), argsWithResult);
                if (messages != null && messages.size() > 0) {
                    for (String message : messages) {
                        messageSender.send(conversationId, message);
                    }
                }
            }
        }
        return result;
    }
}
